/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package queues;

import java.net.BindException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.ws.Endpoint;
import server.ArrayOfCounter;
import server.ArrayOfQueueInfo;
import server.ArrayOfSubject;
import server.QueueInfo;

/**
 * Generic SOAP service to receive notifications from QueuesServer
 * @author Renato Rosa <2101076@my.ipleiria.pt>
 */
public abstract class NotificationsService<T extends NotificationsObserver> {
    public static final String OK_MSG = "OK";
    public static final String NOT_INITIALIZED_MSG = "Not initialized";
    public static final String UNEXPECTED_ARGUMENT_MSG = "Unexpected argument";

    
    protected Endpoint serverEndpoint = null;

    /**
     * Get the value of serverEndpoint
     *
     * @return the value of serverEndpoint
     */
    public Endpoint getServerEndpoint() {
        return serverEndpoint;
    }

    //the ones interested in notifications received
    protected List<T> observers = new ArrayList<>();
    
    //the executor of the service: a single thread
    protected ExecutorService singleThread;

    protected abstract Endpoint publish(String ipAddress);

    public void addObserver(T observer) {
        observers.add(observer);
    }

    public void removeObserver(T observer) {
        observers.remove(observer);
    }

    /**
     * 
     * @param useLocalhost whether the service should be published in localhost or use a external address
     * @throws SocketException when unable to publish
     */
    public void start(boolean useLocalhost) throws SocketException {
        if (!isListening()) {
            singleThread = Executors.newSingleThreadExecutor();
            serverEndpoint = publish(useLocalhost ? "localhost" : getIPAddress());
        }
    }

    public void stop() {
        if (isListening()) {
            //stop the service
            serverEndpoint.stop();
            //shutdown the service thread
            singleThread.shutdown();
            System.out.println("Service stopped.");
        }
    }

    public boolean isListening() {
        return serverEndpoint != null && serverEndpoint.isPublished();
    }

    @WebMethod
    public String initialize(@WebParam @XmlElement(required = true) ArrayOfSubject subjects,
            @WebParam @XmlElement(required = true) ArrayOfCounter counters,
            @WebParam @XmlElement(required = true) ArrayOfQueueInfo tickets) {
        for (NotificationsObserver obs : observers) {
            try {
                obs.initialize(subjects.getSubject(), counters.getCounter(), tickets.getQueueInfo());
            } catch (NotInitializedException ex) {
                return NOT_INITIALIZED_MSG;
            }
        }
        return "OK";
    }

    @WebMethod
    public String update(@WebParam @XmlElement(required = false, nillable = true) ArrayOfCounter counters,
            @WebParam @XmlElement(required = false, nillable = true) ArrayOfQueueInfo tickets) {
        for (NotificationsObserver obs : observers) {
            try {
                obs.updateAll(counters.getCounter(), tickets.getQueueInfo());
            } catch (NotInitializedException ex) {
                return NOT_INITIALIZED_MSG;
            } catch (UnknownSubjectException ex) {
                return UNEXPECTED_ARGUMENT_MSG;
            }
        }
        return OK_MSG;
    }

    @WebMethod
    public String updateQueue(@WebParam @XmlElement(required = true) QueueInfo info) {
        for (NotificationsObserver obs : observers) {
            try {
                obs.updateQueue(info);
            } catch (NotInitializedException ex) {
                return NOT_INITIALIZED_MSG;
            } catch (UnknownSubjectException ex) {
                return UNEXPECTED_ARGUMENT_MSG;
            }
        }
        return OK_MSG;
    }

    @WebMethod
    public String ping() {
        return "Pong :)";
    }

    /**
     * 
     * @return a functional network address in string format
     * @throws SocketException 
     */
    private String getIPAddress() throws SocketException {
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        while (interfaces.hasMoreElements()) {
            NetworkInterface current = interfaces.nextElement();
            if (current.isUp() && !current.isLoopback() && !current.isVirtual()) {
                Enumeration<InetAddress> addresses = current.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress current_addr = addresses.nextElement();
                    if (!current_addr.isLoopbackAddress()) {
                        return current_addr.getHostAddress();
                    }
                }
            }
        }
        throw new BindException("No functional interface address was found.");
    }
}
